import Vue, { VueConstructor, PluginFunction } from 'vue'
import { Locale, Locales } from '@/messages/locale'
import SimpleMessageSource, { MessageSource } from '@/messages/message-source'
import { bundles } from '@/messages/resource-bundle'
import { Watch, Component } from 'vue-property-decorator'
import { ComponentOptions } from 'vue/types/options'

export class VueMessages {
  locale: { locale: Locale }
  messageSource: MessageSource
  listeners: Vue[]

  constructor (locale: Locale, messages: { [x: string]: object }) {
    const _this = this
    this.locale = new Vue({
      data () {
        return {
          locale: locale
        }
      },
      watch: {
        locale (val, oval) {
          if (val !== oval) {
            _this.listeners.forEach(listen =>
              Vue.nextTick(() => listen.$forceUpdate())
            )
          }
        }
      }
    })
    this.messageSource = new SimpleMessageSource(locale)
    this.listeners = []
    bundles.clean()
    Object.keys(messages).forEach(element => {
      bundles.addBundle(Locales.forName(element), messages[element])
    })
  }

  changeLocale (locale: Locale) {
    this.locale.locale = locale
  }

  subscribe (vue: Vue): void {
    this.listeners.push(vue)
  }

  unsubstribe (vue: Vue): void {
    const index = this.listeners.indexOf(vue)
    if (index > -1) {
      this.listeners.splice(index, 1)
    }
  }

  _t (code: string, args: string[], defaultMessage?: string) {
    return this.messageSource.getMessage(
      code,
      args,
      this.locale.locale,
      defaultMessage
    )
  }
}

export type VueMessagesOption = {
  locale: Locale
  messages: { [x: string]: object }
}

const MessageMixin: ComponentOptions<Vue> = {
  beforeCreate () {
    const options = this.$options

    const i18n = options.i18n
    if (i18n) {
      if(i18n instanceof VueMessages) {
        this._i18n = i18n
      } else {
        const message = new VueMessages(i18n.locale, i18n.messages)
        this._i18n = message
      }
      this._i18n.subscribe(this)
    } else if (this.$root && this.$root.$i18n) {
      // root i18n
      this._i18n = this.$root.$i18n
      this._i18n.subscribe(this)
    }
  },
  beforeDestroy () {
    if (!this._i18n) {
      return
    }
    this._i18n.unsubstribe(this)
    this._i18n = null
  }
}

export const install: PluginFunction<VueMessagesOption> = (
  vue: VueConstructor<Vue>,
  options
) => {
  Object.defineProperty(Vue.prototype, '$i18n', {
    get () {
      return this._i18n
    }
  })

  Vue.prototype.$t = function (code: string, ...values: string[]): string {
    const _this = this as Vue
    const i18n = _this.$i18n
    const args = values || []
    return i18n._t(code, args)
  }

  Vue.mixin(MessageMixin)
}
